import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;


public class Board {

	public static final int MIN_FILE = 1;
	public static final int MAX_FILE = 8;
	public static final int MIN_RANK = 1;
	public static final int MAX_RANK = 8;
	
	private Move[] moves;
	
	private HashMap<BoardCell, Piece> cells;
	private ArrayList<Piece> playerPieces, opponentPieces;
	private HashMap<Piece, PieceMoves> pieceMoves;
	private HashMap<BoardCell, Piece> kingAttackers;
	private HashSet<BoardCell> cellsToBlock;
	
	// make a board structure by populating a HashMap with the pieces' position
	public Board(Piece[] pieces, Move[] pastMoves, int playerColor) {
		setMovesArray(pastMoves);
		cells = new HashMap<BoardCell, Piece>();
		cellsToBlock = new HashSet<BoardCell>();
		pieceMoves = new HashMap<Piece, PieceMoves>();
		kingAttackers = new HashMap<BoardCell, Piece>();
		playerPieces = new ArrayList<Piece>();
		opponentPieces = new ArrayList<Piece>();

		for (Piece piece : pieces) {
			BoardCell cell = new BoardCell(piece.getFile(), piece.getRank());
			cell.setPiece(piece);
			cells.put(cell, piece);
			
			if(piece.getColor() == playerColor) playerPieces.add(piece);
			else opponentPieces.add(piece);
		}
	}
	
	public Board(ArrayList<Piece> playerPieces, ArrayList<Piece> opponentPieces,
			Piece pieceToMove, int newFile, int newRank, Move[] pastMoves, int playerColor) {
		setMovesArray(pastMoves);
		this.cells = new HashMap<BoardCell, Piece>();
		this.cellsToBlock = new HashSet<BoardCell>();
		this.pieceMoves = new HashMap<Piece, PieceMoves>();
		this.kingAttackers = new HashMap<BoardCell, Piece>();
		this.playerPieces = playerPieces;
		this.opponentPieces = opponentPieces;
		
		for (Piece piece : playerPieces) {
			BoardCell cell = new BoardCell(piece.getFile(), piece.getRank());
			cell.setPiece(piece);
			cells.put(cell, piece);
		}
		for (Piece piece : opponentPieces) {
			int file = piece.getFile();
			int rank = piece.getRank();
			if(piece.equals(pieceToMove)){
				file = newFile;
				rank = newRank;
			}
			BoardCell cell = new BoardCell(file, rank);
			cell.setPiece(piece);
			cells.put(cell, piece);
		}
	}
	
	// return true if the board has a piece on the given cell
	public boolean hasPieceOn(BoardCell cell) {
		return getPieceOn(cell) != null;
	}

	// return the piece in the given cell of the board, or null if there is no piece there
	public Piece getPieceOn(BoardCell cell) {
		return getCells().get(cell);
	}

	// return a set of cells between two given cells, either vertically, horizontally or diagonally
	public Set<BoardCell> getCellsInBetween(BoardCell start, BoardCell end){
		Set<BoardCell> cellsBetween = new HashSet<BoardCell>();

		if (start.isSameFile(end)) cellsBetween = getPathBetweenFile(start, end);
		else if (start.isSameRank(end)) cellsBetween = getPathBetweenRank(start, end);
		else if (start.isInDiagonalWith(end)) cellsBetween = getPathBetweenDiagonal(start, end);

		return cellsBetween;
	}
	
	public Set<Piece> getPiecesBetween(BoardCell start, BoardCell end){
		Set<Piece> piecesInBetween = new HashSet<Piece>();

		if (start.isSameFile(end)) piecesInBetween = clearPathInFile(start, end);
		else if (start.isSameRank(end)) piecesInBetween = clearPathInRank(start, end);
		else if (start.isInDiagonalWith(end)) piecesInBetween = clearPathInDiagonal(start, end);

		return piecesInBetween;
	}
	
	// return true if there is a clear path between two cells,
	// either vertically, horizontally or diagonally
	public boolean hasPathBetween(BoardCell start, BoardCell end) {
		boolean clearPathExists = false;

		if (start.equals(end)) clearPathExists = false;
		else if (start.isSameFile(end)) clearPathExists = clearPathInFile(start, end).isEmpty();
		else if (start.isSameRank(end)) clearPathExists = clearPathInRank(start, end).isEmpty();
		else if (start.isInDiagonalWith(end)) clearPathExists = clearPathInDiagonal(start, end).isEmpty();

		return clearPathExists;
	}
	
	// will return the set of cells that share the same rank between two given cells
	public Set<BoardCell> getPathBetweenRank(BoardCell start, BoardCell end) {
		Set<BoardCell> cellsBetween = new HashSet<BoardCell>();
		for (int file = MIN_FILE; file <= MAX_FILE; file++) {
			if (fileBetween(file, start.getFile(), end.getFile())) {
				cellsBetween.add(new BoardCell(file, start.getRank()));
			}
		}
		return cellsBetween;
	}

	// return true if there is a clear path between two cells in the same rank
	private Set<Piece> clearPathInRank(BoardCell start, BoardCell end) {
		HashSet<Piece> piecesInBetween = new HashSet<Piece>();

		Set<BoardCell> cellsBetween = getPathBetweenRank(start, end);

		for (BoardCell cell : cellsBetween) {
			if (hasPieceOn(cell)) {
				piecesInBetween.add(getPieceOn(cell));
			}
		}

		return piecesInBetween;
	}

	// will return the set of cells that share the same file between two given cells
	public Set<BoardCell> getPathBetweenFile(BoardCell start, BoardCell end) {
		Set<BoardCell> cellsBetween = new HashSet<BoardCell>();
		for (int rank = MIN_RANK; rank <= MAX_RANK; rank++) {
			if (rankBetween(rank, start.getRank(), end.getRank())) {
				cellsBetween.add(new BoardCell(start.getFile(), rank));
			}
		}
		return cellsBetween;
	}
	
	// return true if there is a clear path between two cells in the same file
	private Set<Piece> clearPathInFile(BoardCell start, BoardCell end) {
		HashSet<Piece> piecesInBetween = new HashSet<Piece>();

		Set<BoardCell> cellsBetween = getPathBetweenFile(start, end);

		for (BoardCell cell : cellsBetween) {
			if (hasPieceOn(cell)) {
				piecesInBetween.add(getPieceOn(cell));
			}
		}

		return piecesInBetween;
	}

	// return true if the given rank is in between the two other given ranks
	private boolean rankBetween(int rank, int start, int end) {
		if (rank > start && rank < end) {
			return true;
		} else if (rank > end && rank < start) {
			return true;
		} else return false;
	}

	// return true if the given file is in between the two other given ranks
	private boolean fileBetween(int file, int start, int end) {
		if (file > start && file < end) {
			return true;
		} else if (file > end && file < start) {
			return true;
		} else return false;
	}

	// will return the set of cells that share a same diagonal between two given cells
	public Set<BoardCell> getPathBetweenDiagonal(BoardCell start, BoardCell end){
		Set<BoardCell> cellsBetween = new HashSet<BoardCell>();
		int rankAdjustment = (start.getRank() < end.getRank() ? 1 : -1);
		int fileAdjustment = (start.getFile() < end.getFile() ? 1 : -1);
		int rank = start.getRank() + rankAdjustment;
		int file = start.getFile() + fileAdjustment;

		while (rank != end.getRank()) {
			cellsBetween.add(new BoardCell(file, rank));
			rank = rank + rankAdjustment;
			file = file + fileAdjustment;
		}

		return cellsBetween;
	}
	
	// return true if there is a clear path between two cells in a same diagonal
	private Set<Piece> clearPathInDiagonal(BoardCell start, BoardCell end) {
		HashSet<Piece> piecesInBetween = new HashSet<Piece>();

		int rankAdjustment = (start.getRank() < end.getRank() ? 1 : -1);
		int fileAdjustment = (start.getFile() < end.getFile() ? 1 : -1);
		int rank = start.getRank() + rankAdjustment;
		int file = start.getFile() + fileAdjustment;

		while (rank != end.getRank()) {
			if (hasPieceOn(new BoardCell(file, rank))) {
				piecesInBetween.add(getPieceOn(new BoardCell(file, rank)));
			}
			rank = rank + rankAdjustment;
			file = file + fileAdjustment;
		}

		return piecesInBetween;
	}
	
	// return the cell where there is a given piece, or null if there is no such cell
	public BoardCell getCellWithPieceOn(Piece piece){
		Set<BoardCell> setCells = getCells().keySet();
		for (Iterator<BoardCell> cellIter = setCells.iterator(); cellIter.hasNext();) {
			BoardCell cell = cellIter.next();
			Piece p = getPieceOn(cell);
			if (p.equals(piece)) {
				return cell;
			}
		}
		return null;
	}
	
	public HashMap<BoardCell, Piece> isKingInCheck(int color){
		BoardCell kingCell = getKingCell(color);
		HashMap<BoardCell, Piece> attackers = getAttackers(kingCell);
		getKingAttackers().clear();
		getKingAttackers().putAll(attackers);
		
		cellsToBlock.clear();
		for (BoardCell attacker : attackers.keySet()) {
			Set<BoardCell> cellsInPath = getCellsInBetween(attacker, kingCell);
			if(!cellsInPath.isEmpty()) cellsToBlock.addAll(cellsInPath);
			cellsToBlock.add(attacker);
		}
		return attackers;
	}
	
	public HashMap<BoardCell, Piece> getAttackers(BoardCell target) {
		HashMap<BoardCell, Piece> map = new HashMap<BoardCell, Piece>();
		BoardMoveGenerator bmv = new BoardMoveGenerator(this, true);
		for (Piece p : getOpponentPieces()) {
			if(!getPieceMoves().containsKey(p)){
				getPieceMoves().put(p, bmv.getPossibleMoves(p));
			}

			PieceMoves moves = getPieceMoves().get(p);
			
			if(p.getKind() == Piece.TYPE_PAWN){
				if(target.getRank() == p.getRank()+(p.getColor() == Piece.COLOR_WHITE ? 1 : -1)){
					if(target.getFile() == p.getFile()-1){
						BoardMove bMove = new BoardMove(new BoardCell(p.getFile()-1, target.getRank()));
						if(!moves.getAttacks().contains(bMove)){
							moves.getAttacks().add(bMove);							
						}
					}else if(target.getFile() == p.getFile()+1){
						BoardMove bMove = new BoardMove(new BoardCell(p.getFile()+1, target.getRank()));
						if(!moves.getAttacks().contains(bMove)){
							moves.getAttacks().add(bMove);							
						}
					}
				}
			}
			
			BoardCell cell = getCellWithPieceOn(p);
			for (BoardMove move : moves.getAttacks()) {
				if(move.getToFile() == target.getFile()
						&& move.getToRank() == target.getRank()){
					map.put(cell, p);
				}
			}
		}
		return map;
	}
	
	public HashMap<BoardCell, Piece> getAttackersOrMovers(BoardCell target, BoardMoveGenerator bmv, boolean testingCheck) {
		HashMap<BoardCell, Piece> map = new HashMap<BoardCell, Piece>();
		bmv.setTestingCheck(testingCheck);
		for (Piece p : getOpponentPieces()) {
			if(p.getKind() == Piece.TYPE_KING) continue; // a king will never attack the other
			
			if(!getPieceMoves().containsKey(p)){
				getPieceMoves().put(p, bmv.getPossibleMoves(p));
			}
			BoardCell cellWithPieceOn = getCellWithPieceOn(p);

			PieceMoves moves = getPieceMoves().get(p);
			if(p.getKind() == Piece.TYPE_PAWN){ //TODO gambiarra - to repetindo o codigo da funcao acima
				if(target.getRank() == p.getRank()+(p.getColor() == Piece.COLOR_WHITE ? 1 : -1)){
					if(target.getFile() == p.getFile()-1){
						map.put(cellWithPieceOn, p);
					}else if(target.getFile() == p.getFile()+1){						
						map.put(cellWithPieceOn, p);
					}
				}
			}else{
				for (BoardMove move : moves.getAllMoves()) {
					if(move.getToFile() == target.getFile()
							&& move.getToRank() == target.getRank()){
						map.put(cellWithPieceOn, p);
					}
				}
			}
		}
		bmv.setTestingCheck(!bmv.isTestingCheck());
		return map;
	}
	
	// get the cell where the king of the given color is located
	public BoardCell getKingCell(int color) {
		Set<BoardCell> setCells = getCells().keySet();
		for (Iterator<BoardCell> cellIter = setCells.iterator(); cellIter.hasNext();) {
			BoardCell cell = cellIter.next();
			Piece piece = getPieceOn(cell);
			if (piece.getKind() == Piece.TYPE_KING && piece.getColor() == color) {
				return cell;
			}
		}

		return null;
	}
	
	// return the cell with the given file and rank
	public BoardCell getCell(int file, int rank){
		Set<BoardCell> setCells = getCells().keySet();
		for (Iterator<BoardCell> cellIter = setCells.iterator(); cellIter.hasNext();) {
			BoardCell cell = cellIter.next();
			if(cell.getFile() == file && cell.getRank() == rank) return cell;
		}
		
		return null;
	}
	
	public HashMap<BoardCell, Piece> getCells() {
		return cells;
	}
	
	public HashMap<Piece, PieceMoves> getPieceMoves() {
		return pieceMoves;
	}
	
	public ArrayList<Piece> getPlayerPieces() {
		return playerPieces;
	}
	
	public ArrayList<Piece> getOpponentPieces() {
		return opponentPieces;
	}

	public HashMap<BoardCell, Piece> getKingAttackers() {
		return kingAttackers;
	}
	
	public void setKingAttackers(HashMap<BoardCell, Piece> kingAttackers) {
		this.kingAttackers = kingAttackers;
	}
	
	public HashSet<BoardCell> getCellsToBlock() {
		return cellsToBlock;
	}
	
	public Move getLastMove() {
		return moves.length > 0 ? moves[0] : null;
	}
	
	public void setMovesArray(Move[] moves) {
		this.moves = moves;
	}
	
	public Move[] getMovesArray() {
		return moves;
	}
	
	public int getPlayerColor(){
		return getPlayerPieces().get(0).getColor();
	}
	
	public int getOpponentColor(){
		return getOpponentPieces().get(0).getColor();
	}
	
}
